package com.dynamo.transport.network.practise.channel.socket.nio;

import com.dynamo.transport.network.practise.channel.Channel;
import com.dynamo.transport.network.practise.channel.ChannelFuture;

import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author duanyimiao
 * @create 2018-12-17 11:11 AM
 * @description
 **/
public abstract class AbstractNioSelector implements NioSelector {

    protected Selector selector;

    private AtomicBoolean wakeUp = new AtomicBoolean(false);

    private Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();


    public AbstractNioSelector() {
        open();
    }

    private void open() {
        try {
            selector = Selector.open();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void register(Channel channel, ChannelFuture future) {
        Runnable task = createTask(channel, future);
        taskQueue.add(task);
        //多线程情况下只唤醒selector一次，然后从队列中取出任务执行(这些任务就是将某个事件注册到selector上)
        if (wakeUp.compareAndSet(false, true)) {
            selector.wakeup();
        }
    }

    @Override
    public void run() {

        for (; ; ) {
            try {
                //TODO 当服务端没有开启服务时，客户端去连接会导致selector.select()一直返回0 并不会阻塞，所以需要考虑解决死循环一直占用cpu问题，其中如果client强制杀死进程(并不是主动close channel)时也会导致server端的空轮询
                System.out.println("worker run");
                selector.select();
                processTask();
                process(selector.selectedKeys());

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void processTask() {
        for (; ; ) {
            //add 和 offer 如果队列已经满了add会抛出移除而offer不会
            //remove和poll 如何队列为空remove排除NoSuchElementException poll只会返回null
            //element和peek element查询头部元素不存在则抛出NoSuchElementException
            Runnable task = taskQueue.poll();
            if (task == null) {
                break;
            }
            task.run();
        }
    }

    public abstract Runnable createTask(Channel channel, ChannelFuture future);

    public abstract void process(Set<SelectionKey> keys);

}
